Skip to content

Use zig assert for apicheck - #213

Merged
natecraddock merged 2 commits into
mainfrom
apicheck
Oct 24, 2026
Merged

natecraddock merged 2 commits into
mainfrom
apicheck

Conversation

@natecraddock

@natecraddock natecraddock commented May 10, 2026 •

Copy link
Copy Markdown
Owner

Using a zig assert for apicheck enables stack traces for panics. This works by adding an assert.h implementation that Lua will find when searching for <assert.h> that calls a Zig assert function (with stack trace) instead of C's assert().

Before

~/dev/sandbox/ziglua-test-raise $ zig-out/bin/ziglua_test_raise
hello world
ziglua_test_raise: zig-pkg/N-V-__8AAPqeFQDipy0CdI6MKBmwYYarybBTO3IIJPrSzH_w/src/lapi.c:193: void lua_settop(lua_State *, int): Assertion `-(idx+1) <= (L->top.p - (func + 1))' failed.
fish: Job 2, 'zig-out/bin/ziglua_test_raise' terminated by signal SIGABRT (Abort)

After

~/dev/sandbox/ziglua-test-raise $ zig-out/bin/ziglua_test_raise
hello world
thread 86946 panic: reached unreachable code
/home/nathan/.local/bin/zig-0.17.0/lib/std/debug.zig:442:14: 0x103d9c9 in assert (std.zig)
    if (!ok) unreachable; // assertion failure
             ^
/home/nathan/dev/ziglua/src/lib.zig:117:21: 0x1205868 in zlua_assert (lib.zig)
    std.debug.assert(ok);
                    ^
zig-pkg/N-V-__8AAPqeFQDipy0CdI6MKBmwYYarybBTO3IIJPrSzH_w/src/lapi.c:193:5: 0x12182d6 in lua_settop (zig-pkg/N-V-__8AAPqeFQDipy0CdI6MKBmwYYarybBTO3IIJPrSzH_w/src/lapi.c)
    api_check(L, -(idx+1) <= (L->top.p - (func + 1)), "invalid new top");
    ^
/home/nathan/dev/ziglua/src/lib.zig:2687:21: 0x11e4ecd in setTop (lib.zig)
        c.lua_settop(@ptrCast(lua), index);
                    ^
/home/nathan/dev/ziglua/src/lib.zig:2011:19: 0x11e4e31 in pop (lib.zig)
        lua.setTop(-n - 1);
                  ^
/home/nathan/dev/sandbox/ziglua-test-raise/src/main.zig:17:12: 0x11d9f48 in main (main.zig)
    lua.pop(1000);
           ^
/home/nathan/.local/bin/zig-0.17.0/lib/std/start.zig:827:30: 0x11da7d1 in callMain (std.zig)
    return wrapMain(root.main(.{
                             ^
../sysdeps/nptl/libc_start_call_main.h:58:16: 0x773f8ee2a1c9 in __libc_start_call_main (../sysdeps/x86/libc-start.c)
../csu/libc-start.c:360:3: 0x773f8ee2a28a in __libc_start_main_impl (../sysdeps/x86/libc-start.c)
???:?:?: 0x1217e14 in _start (???)
fish: Job 2, 'zig-out/bin/ziglua_test_raise' terminated by signal SIGABRT (Abort)

This works for Lua 5.1 through 5.5. LuaJIT is not supported. This PR does enable basic apicheck support for LuaJIT though.

Luau seems to always do apicheck with a stack trace, so this has no impact on Luau.

If for some reason this causes issues, users can opt-out by setting apicheck_type = .basic when adding the dependency

const zlua = b.dependency("zlua", .{
    .lang = .lua55,
    .apicheck_type = .basic,
    .optimize = optimize,
});

@natecraddock natecraddock linked an issue May 10, 2026 that may be closed by this pull request
@natecraddock

Copy link
Copy Markdown
Owner Author

It's looking like it might not be possible to get this working in Lua 5.5 currently.

Other versions of Lua work like this:

  1. The user.h file creates this define #define luai_apicheck(l,e) zlua_assert(e)
  2. luai_apicheck is later used in the definition of lua_assert

Lua 5.5 removed luai_apicheck. If you try to define lua_assert in the user h file, a later import from lapi.h unconditionally defines lua_assert which overrides the custom one from the user h file.

I tried patching the lapi.h header file to remove those definitions, but since headers are searched for in directories I couldn't find a way to exclude the original unpatched lapi.h. Maybe copy all of the other headers and the patched header to a new directory?

I'm considering posting on the Lua mailing list about this change in behavior in Lua 5.5 and see what comes up

@robbielyman

robbielyman commented May 11, 2026 •

Copy link
Copy Markdown
Collaborator

@natecraddock looking through the source, I suspect one can do

#undefine LUA_USE_APICHECK
#define api_check(l,e,msg)     zlua_assert(l,e,msg)

hmmm... apparently not...

@natecraddock

Copy link
Copy Markdown
Owner Author

I looked into this some more, and I also cannot redefine luai_apicheck in LuaJIT. I wonder if it is worthwhile to merge for Lua 5.1 - 5.4 and then maybe include a way to set up something like you suggested here #71 (comment) for the other versions

@robbielyman

Copy link
Copy Markdown
Collaborator

Yeah, I think it's worth merging!

For Lua 5.1, 5.2, 5.3, 5.4, and 5.5, this configures the Lua "api check"
to use Zig's std.debug.assert() instead of C's assert() from <assert.h>.
This includes stack traces which makes the assert a million times more
helpful.

LuaJIT does not use assert() so this does not work for LuaJIT, but this
commit does enable basic api check support for LuaJIT.

Enables these stack trace assertions by default. Users can opt-out by
setting apicheck_type to .default when adding ziglua as a dependency.
@natecraddock

Copy link
Copy Markdown
Owner Author

@robbielyman I got this working in Lua 5.5! And the implementation is overall much simpler. See the updated description if you are curious

Feel free to take a look. I'll probably merge this in about a day.

@robbielyman

Copy link
Copy Markdown
Collaborator

Oh, very nice solution! Good work 😁

@natecraddock
natecraddock merged commit 6d80dc0 into main Oct 24, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Define apicheck function to use a Zig panic

2 participants